// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"fmt"
	"text/template/parse"
)

// 错误描述了模板转义过程中遇到的问题。
type Error struct {
	// 错误代码描述了错误的类型。
	ErrorCode ErrorCode
	// 节点是导致问题的节点（如果已知）。
	// 如果不是nil，则覆盖Name和Line。
	Node parse.Node
	// Name是遇到错误的模板的名称。
	Name string
	// Line是模板源中错误的行号或0。
	Line int
	// 说明是对问题的可读说明。
	Description string
}

// ErrorCode是一种错误的代码。
type ErrorCode int

// 我们为转义模板时显示的每个错误定义代码，但
// 转义模板也可能在运行时失败。
// 
// 输出：“ZgotmplZ”
// 示例：
// /<img src=“{.X}”>
// 其中{.X}的计算结果为`javascript:…`
// 讨论：
// /“ZgotmplZ”是一个特殊值，指示不安全内容在运行时到达
// CSS或URL上下文。该示例的输出将是
// <img src=“#ZgotmplZ”>
// 如果数据来自受信任的源，请使用内容类型使其免于过滤：URL（`javascript:…`）。
const (
	// OK表示没有错误。
	OK ErrorCode = iota

	// errrambigcontext:“……出现在URL”
	// 示例：
	// <a href=“
	// {if.C}}
	// /path/
	// {else}
	// /search？q=
	// {end}
	// /{{.X}
	// />
	// 讨论：
	// /{.X}处于不明确的URL上下文中，因为根据{{.C}，
	// 它可能是URL后缀或查询参数。
	// 将{.X}移动到条件中可以消除歧义：
	// /<a href=“{{if.C}}/path/{{.X}}{{else}/search？q={.X}”>
	ErrAmbigContext

	// /ErrBadHTML:“预期的空格、属性名或标记结尾，但得到…”，
	// 示例：
	// <a href=/search？q=foo>
	// <href=foo>
	// <form na<e=…>
	// <option selected<
	// 讨论：
	// 这通常是由于HTML元素的输入错误，但是有些符文
	// 在标记名、属性名和未加引号的属性
	// 值中被禁止，因为它们会引起解析器的歧义。
	// 引用所有属性是最好的策略。
	ErrBadHTML

	// errbranchen:“{if}}分支在不同的上下文中结束”
	// 示例：
	// /{if.C}}<a href=“{{end}}{{.X}}}
	// 讨论：
	// 包html/模板通过
	// 该示例是不明确的，因为{X}可能是HTML文本节点、
	// 或HTML属性中的URL前缀。{X}的上下文是
	// 用于确定如何转义，但该上下文取决于
	// 运行时{.C}这不是静态已知的。
	// 
	// 问题通常是缺少引号或尖括号，或者可以通过重构将两个上下文
	// 放入if、范围或with的不同分支中来避免。如果问题
	// 位于{{range}在一个永远不应该为空的集合上，添加一个虚拟的{{else}会有所帮助。
	ErrBranchEnd

	// ErrEndContext:“…在非文本上下文中结束：…”
	// 示例：
	// /<div 
	// /<div title=“no close quote>
	// /<script>f（）
	// 讨论：
	// 执行的模板应生成HTML的DocumentFragment。
	// 结束时没有关闭标记的模板将触发此错误。
	// 不应在HTML上下文中使用的模板或
	// 生成不完整的片段不应直接执行。
	// 
	// /{define“main”}<script>{template“helper”}</script>{{end}}
	// /{define“helper”}文档。写入（'<div title=“{end}}}}}
	// 
	// “helper”无法生成有效的文档片段，因此不应直接执行
	// 。
	ErrEndContext

	// ErrNoSuchTemplate:“没有这样的模板…”
	// 示例：
	// /{{define“main”}}}<div{{{template“attrs”}>{{end}}}
	// /{define“attrs”}href=“{{.URL}}{end}
	// 讨论：
	// 包html/template查看模板调用以计算
	// 上下文。
	// 在这里，当从“main”调用
	// 时，“attrs”中的{.URL}必须被视为URL，但如果在解析“main”时未定义“attrs”，则会出现此错误。
	ErrNoSuchTemplate

	// ErrOutputContext:“无法计算模板的输出上下文…”
	// 示例：
	// /{{define“t”}{{{{if.t}}{{template“t”.t}{{end}{{end}{H}}}，{end}
	// 讨论：
	// 递归模板不在其开始的同一上下文中结束，并且无法计算可靠的输出上下文。
	// 查找命名模板中的键入错误。
	// 如果不应在命名的开始中调用模板context，
	// 在意外的上下文中查找对该模板的调用。
	// 可能会将递归模板重构为非递归的。
	ErrOutputContext

	// ErrPartialCharset:“未完成的JS regexp字符集…”
	// 示例：
	// /<script var pattern=/foo[{.Chars}]/</script>
	// 讨论：
	// 包html/template不支持插入到常规的
	// 表达式文字字符集。
	ErrPartialCharset

	// errpartialscape:“未完成的转义序列在…”
	// 示例：
	// /<script>警报（\{.X}}）</script>
	// 讨论：
	// 程序包html/template不支持在
	// 反斜杠之后的操作。
	// 这通常是一个错误，有更好的解决方案；对于
	// 示例
	// /<script>警报（{.X}）</script>
	// 应该可以工作，如果{.X}是一个部分转义序列，如
	// “xA0”，则将整个序列标记为安全内容：JSStr（`\xA0`）
	ErrPartialEscape

	// ErrRangeLoopReentry:“范围内循环重入：…”
	// 示例：
	// /<script X=[{range.}}}}{{end}}]</script>
	// 讨论：
	// 如果一个范围的迭代会导致它在一个
	// 与前面的过程不同的上下文中结束，则没有单一上下文。
	// 在示例中，缺少一个引号，因此不清楚
	// 是否{.}表示位于JS字符串或JS值
	// 上下文中。第二次迭代将生成类似
	// 
	// /<script>var x=['firstValue，'secondValue]</script>
	ErrRangeLoopReentry

	// ErrSlashAmbig:'/'可以开始一个除法或regexp.
	// 示例：
	// /<script>
	// /{if.C}var x=1{end}}
	// /-{N}/i.test（x）？doThis:doThat（）上面的例子可以产生` var x=1/-2/i.test.`第一个'/'是一个数学除法运算符，或者它可以产生`/-2/i.test.`第一个'/'是一个除法运算符`其中，第一个“/”开始一个
	// regexp literal.
	// 查找分支中缺少的分号，并可能添加
	// 括号以明确您想要的解释。
	ErrSlashAmbig

	// ErrPredefinedEscaper:“预定义的转义符…在模板中不允许。”“
	// 示例：
	// <div class={{.| html}”>Hello<div>
	// 讨论：
	// 包html/模板已在上下文中转义所有管道到
	// 生成html输出，防止代码注入。手动转义
	// 使用预定义转义符“html”或“urlquery”进行管道输出
	// 是否不必要，可能会影响Go 1.8及更早版本中转义的
	// 管道输出的正确性或安全性。
	// 
	// 在大多数情况下，例如给定的示例，此错误可以通过
	// 从管道，并让
	// 上下文自动转义器处理管道的转义。在其他
	// 将继续被允许作为管道中的最后一个命令。但是，如果
	// 管道出现在一个不带引号的属性值上下文中，“html”是不允许的。避免在新模板中完全使用“html”和“urlquery”。
	ErrPredefinedEscaper
)

func (e *Error) Error() string {
	switch {
	case e.Node != nil:
		loc, _ := (*parse.Tree)(nil).ErrorContext(e.Node)
		return fmt.Sprintf("html/template:%s: %s", loc, e.Description)
	case e.Line != 0:
		return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
	case e.Name != "":
		return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description)
	}
	return "html/template: " + e.Description
}

// errorf在给定格式字符串f和args的情况下创建错误。
// 模板名称仍需提供。
func errorf(k ErrorCode, node parse.Node, line int, f string, args ...interface{}) *Error {
	return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
}
